<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"ala
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>modbus EPICS driver support</title>
  <meta content="text/html; charset=ISO-8859-1" http-equiv="Content-Type" />
</head>
<body>
  <div style="text-align: center">
    <h1>
      <b>modbus</b></h1>
    <h1>
      Driver Support for Modbus Protocol under EPICS</h1>
    <h2>
      Release 2-4</h2>
    <h2>
      Mark Rivers</h2>
    <h2>
      May 22, 2012</h2>
    <hr />
  </div>
  <h2>
    Contents</h2>
  <ul>
    <li><a href="#Acknowledgments">Acknowledgments</a></li>
    <li><a href="#Overview_of_Modbus">Overview of Modbus</a></li>
    <li><a href="#Driver_architecture">Driver architecture</a></li>
    <li><a href="#Creating_a_modbus_port_driver">Creating a <b>modbus</b> port driver</a></li>
    <li><a href="#EPICS_device_support">EPICS device support</a></li>
    <li><a href="#Example_applications">Example applications</a></li>
    <li><a href="#medm_screens">medm screens</a></li>
    <li><a href="#Debug_tracing">Debug tracing</a></li>
    <li><a href="#Differences_from_Triumf_modtcp_and_plctcp_package">Differences from
      Triumf modtcp and plctcp package</a></li>
    <li><a href="#Known_problems">Known problems</a></li>
  </ul>
  <h2 id="Acknowledgments">
    Acknowledgments</h2>
  <p>
    The <b>modbus</b> package is based on the <a href="http://isacwserv.triumf.ca/epics/modtcp/TRIUMFmodtcp.html">
      modtcp and plctcp packages </a>written by Rolf Keitel from Triumf. The <b>modtcp</b>
    package was originally converted to Linux by Ivan So from NSLS. <b>modbus</b> was
    extensively re-written for conversion to EPICS 3.14 and to use the EPICS asyn module.
    It now contains little of the original <b>modtcp</b> code, but retains much of the
    original architecture.</p>
  <h2 id="Overview_of_Modbus">
    Overview of Modbus</h2>
  <p>
    MODBUS is an application layer messaging protocol, positioned at level 7 of the
    OSI model, that provides client/server communication between devices connected on
    different types of buses or networks. It is typically used for communication with
    I/O systems, including Programmable Logic Controllers (PLCs).</p>
  <h3>
    Modbus communication links</h3>
  <p>
    Modbus supports the following 3 communication-link layers:
  </p>
  <table border="1" cellpadding="2" cellspacing="2" style="text-align: left">
    <tbody>
      <tr>
        <th align="center" colspan="2,">
          Modbus Communication Links</th>
      </tr>
      <tr>
        <th>
          Link type</th>
        <th>
          Description</th>
      </tr>
      <tr>
        <td>
          TCP</td>
        <td>
          TCP/IP using standard port 502.</td>
      </tr>
      <tr>
        <td>
          RTU</td>
        <td>
          RTU uses an additional CRC for packet checking. RTU is normally run over serial
          communicaton linke, i.e. RS-232, RS-422, or RS-485. The protocol directly transmits
          each byte as 8 data bits, so uses "binary" rather than ASCII encoding. When using
          serial links start and end of message frames is detected by timing rather than by
          specific characters. RTU can also be run over TCP, though this is less common than
          the standard Modbus TCP without RTU. </td>
      </tr>
      <tr>
        <td>
          Serial ASCII</td>
        <td>
          Serial protocol, which is normally run over serial communication links, i.e. RS-232,
          RS-422, or RS-485. The protocol encodes each byte as 2 ASCII characters. The start
          and end of message frames is detected by specific characters (":" to start a message
          and CR/LF to end a message). This protocol is less efficient than RTU, but may be
          more reliable in some environments. ASCII can also be run over TCP, though this
          is much less common than the standard Modbus TCP.</td>
      </tr>
    </tbody>
  </table>
  <p>
    This <b>modbus</b> package supports all of the above Modbus communication-link layers.
  </p>
  <h3>
    Modbus data types</h3>
  <p>
    Modbus provides access to the following 4 types of data:
  </p>
  <table border="1" cellpadding="2" cellspacing="2" style="text-align: left">
    <tbody>
      <tr>
        <th align="center" colspan="4,">
          Modbus Data Types</th>
      </tr>
      <tr>
        <th>
          Primary tables</th>
        <th>
          Object type</th>
        <th>
          Access</th>
        <th>
          Comments</th>
      </tr>
      <tr>
        <td>
          Discrete Inputs</td>
        <td>
          Single bit</td>
        <td>
          Read-Only</td>
        <td>
          This type of data can be provided by an I/O system.</td>
      </tr>
      <tr>
        <td>
          Coils</td>
        <td>
          Single bit </td>
        <td>
          Read-Write</td>
        <td>
          This type of data can be alterable by an application program.</td>
      </tr>
      <tr>
        <td>
          Input Registers</td>
        <td>
          16-bit word </td>
        <td>
          Read-Only</td>
        <td>
          This type of data can be provided by an I/O system.</td>
      </tr>
      <tr>
        <td>
          Holding Registers </td>
        <td>
          16-bit word </td>
        <td>
          Read-Write</td>
        <td>
          This type of data can be alterable by an application program.</td>
      </tr>
    </tbody>
  </table>
  <h3>
    Modbus communications</h3>
  <p>
    Modbus communication consists of a <i>request message</i> sent from the <i>Modbus
      client</i> to the <i>Modbus server</i>. The server replies with a <i>response message</i>.
    Modbus request messages contain:</p>
  <ul>
    <li>An 8-bit Modbus function code that describes the type of data transfer to be performed.</li>
    <li>A 16-bit Modbus address that describes the location in the server to read or write
      data from.</li>
    <li>For write operations, the data to be transferred.</li>
  </ul>
  <h3>
    Modbus function codes</h3>
  <p>
    <b>modbus</b> supports the following 8 Modbus function codes:
  </p>
  <table border="1" cellpadding="2" cellspacing="2" style="text-align: left">
    <tbody>
      <tr>
        <th align="center" colspan="3,">
          Modbus Function Codes</th>
      </tr>
      <tr>
        <th>
          Access</th>
        <th>
          Function description</th>
        <th>
          Function code</th>
      </tr>
      <tr>
        <td>
          Bit access</td>
        <td>
          Read Coils</td>
        <td>
          1</td>
      </tr>
      <tr>
        <td>
          Bit access</td>
        <td>
          Read Discrete Inputs</td>
        <td>
          2</td>
      </tr>
      <tr>
        <td>
          Bit access</td>
        <td>
          Write Single Coil</td>
        <td>
          5</td>
      </tr>
      <tr>
        <td>
          Bit access</td>
        <td>
          Write Multiple Coils</td>
        <td>
          15</td>
      </tr>
      <tr>
        <td>
          16-bit word access</td>
        <td>
          Read Input Registers</td>
        <td>
          4</td>
      </tr>
      <tr>
        <td>
          16-bit word access</td>
        <td>
          Read Holding Registers</td>
        <td>
          3</td>
      </tr>
      <tr>
        <td>
          16-bit word access</td>
        <td>
          Write Single Register</td>
        <td>
          6</td>
      </tr>
      <tr>
        <td>
          16-bit word access</td>
        <td>
          Write Multiple Registers</td>
        <td>
          16</td>
      </tr>
    </tbody>
  </table>
  <h3>
    Modbus addresses</h3>
  <p>
    Modbus addresses are specified by a 16-bit integer address. The location of inputs
    and outputs within the 16-bit address space is not defined by the Modbus protocol,
    it is vendor-specific. The following table lists some of the commonly used Modbus
    addresses for Koyo PLCs.</p>
  <table border="1" cellpadding="2" cellspacing="2" style="text-align: left">
    <tbody>
      <tr>
        <th align="center" colspan="3,">
          Modbus Addresses for<br />
          Koyo DL05/06/240/250/260/430/440/450 PLCs</th>
      </tr>
      <tr>
        <th>
          PLC Memory Type</th>
        <th>
          Modbus start address
          <br />
          Decimal (octal)</th>
        <th>
          Function codes</th>
      </tr>
      <tr>
        <th align="center" colspan="3,">
          Discrete inputs and coils</th>
      </tr>
      <tr>
        <td>
          Inputs (X)</td>
        <td>
          2048 (04000)</td>
        <td>
          2</td>
      </tr>
      <tr>
        <td>
          Special Relays (SP)</td>
        <td>
          3072 (06000)</td>
        <td>
          2</td>
      </tr>
      <tr>
        <td>
          Outputs (Y)</td>
        <td>
          2048 (04000)</td>
        <td>
          1, 5, 15</td>
      </tr>
      <tr>
        <td>
          Control Relays (C)</td>
        <td>
          3072 (06000)</td>
        <td>
          1, 5, 15</td>
      </tr>
      <tr>
        <td>
          Timer Contacts (T)</td>
        <td>
          6144 (014000)</td>
        <td>
          1, 5, 15</td>
      </tr>
      <tr>
        <td>
          Counter Contacts (CT)</td>
        <td>
          6400 (014400)</td>
        <td>
          1, 5, 15</td>
      </tr>
      <tr>
        <td>
          Stage Status Bits (S)</td>
        <td>
          6144 (012000)</td>
        <td>
          1, 5, 15</td>
      </tr>
      <tr>
        <th align="center" colspan="3,">
          Input registers and holding registers (V memory)</th>
      </tr>
      <tr>
        <td>
          Timer Current Values (TA)</td>
        <td>
          0 (00)</td>
        <td>
          4</td>
      </tr>
      <tr>
        <td>
          Counter Current Values (CTA)</td>
        <td>
          512 (01000)</td>
        <td>
          4</td>
      </tr>
      <tr>
        <td>
          Global Inputs (VGX)</td>
        <td>
          16384 (040000)</td>
        <td>
          4</td>
      </tr>
      <tr>
        <td>
          Global Outputs (VGY)</td>
        <td>
          16512 (040200)</td>
        <td>
          3, 6, 16</td>
      </tr>
      <tr>
        <td>
          Inputs (VX)</td>
        <td>
          16640 (040400)</td>
        <td>
          4</td>
      </tr>
      <tr>
        <td>
          Outputs (VY)</td>
        <td>
          16704 (040500)</td>
        <td>
          3, 6, 16</td>
      </tr>
      <tr>
        <td>
          Control Relays (VC)</td>
        <td>
          16768 (040600)</td>
        <td>
          3, 6, 16</td>
      </tr>
      <tr>
        <td>
          Stage Status Bits (VS)</td>
        <td>
          16896 (041000)</td>
        <td>
          3, 6, 16</td>
      </tr>
      <tr>
        <td>
          Timer Contacts (VT)</td>
        <td>
          16960 (041100)</td>
        <td>
          3, 6, 16</td>
      </tr>
      <tr>
        <td>
          Counter Contacts (VCT)</td>
        <td>
          16992 (041140)</td>
        <td>
          3, 6, 16</td>
      </tr>
      <tr>
        <td>
          Special Relays (VSP)</td>
        <td>
          17024 (041200)</td>
        <td>
          4</td>
      </tr>
    </tbody>
  </table>
  <p>
    Other PLC manufacturers will use different Modbus addresses.
  </p>
  <p>
    Note that 16-bit Modbus addresses are commonly specified with an offset of 400001
    (or 300001). This offset is not used by the <b>modbus</b> driver, it uses only the
    16-bit address, not the offset.
  </p>
  <h3>
    Modbus data length limitations</h3>
  <p>
    Modbus read operations are limited to transferring 125 16-bit words or 2000 bits.
    Modbus write operations are limited to transferring 123 16-bit words or 1968 bits.</p>
  <h3>
    More information on Modbus</h3>
  <p>
    For more information about the Modbus protocol, the official Modbus specification
    can be found <a href="http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf">
      on the Web</a> or in the <b>modbus</b> <a href="Modbus_Application_Protocol_V1_1b.pdf">
        documentation directory.</a>
  </p>
  <p>
    The official specification for Modbus over TCP/IP can be found <a href="http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf">
      on the Web</a> or in the <b>modbus</b> <a href="Modbus_Messaging_Implementation_Guide_V1_0b.pdf">
        documentation directory.</a>
  </p>
  <p>
    The official specification for Modbus over serial can be found <a href="http://www.modbus.org/docs/Modbus_over_serial_line_V1_02.pdf">
      on the Web</a> or in the <b>modbus</b> <a href="Modbus_over_serial_line_V1_02.pdf">
        documentation directory.</a>
  </p>
  <h2 id="Driver_architecture">
    Driver architecture</h2>
  <p>
    <b>CAUTION:</b> <b>modbus</b> can provide access to all of the I/O and memory of
    the PLC. In fact, it is not even necessary to run a ladder logic program in the
    PLC at all. The PLC can be used as a "dumb" I/O subsystem, with all of the logic
    residing in the EPICS IOC. However, if a ladder logic program <i>is</i> being run
    in the PLC then the EPICS access with <b>modbus</b> must be designed carefully.
    For example, the EPICS IOC might be allowed to <i>read</i> any of the PLC I/O points
    (X inputs, Y outputs, etc.), but <i>writes</i> could be restricted to a small range
    of Control Registers, (e.g. C200-C240). The ladder logic would monitor these control
    registers, considering them to be "requests" from EPICS that should be acted upon
    only if it is safe to do so.</p>
  <p>
    The architecture of the <b>modbus</b> module from the top-level down consists of
    the following 4 layers:</p>
  <ol>
    <li><a href="http://www.aps.anl.gov/epics/modules/soft/asyn/R4-7/asynDriver.html#genericEpicsSupport">
      EPICS asyn device support</a>. . This is the general purpose device support provided
      with <a href="http://www.aps.anl.gov/epics/modules/soft/asyn">asyn</a> There is
      no special device support needed or provided with <b>modbus</b>.</li>
    <li>An EPICS asyn port driver that functions as a Modbus client. The <b>modbus</b>
      port driver communicates with EPICS device support (layer 1) using the standard
      asyn interfaces (asynUInt32Digital, asynInt32, etc.). This driver sends and receives
      device-independent Modbus frames via the standard asynOctet interface to the "interpose
      interface" (layer 3). These frames are independent of the underlying communications
      protocol.</li>
    <li>An asyn "interpose interface" layer that handles the additional data required
      by the underlying communications layer (TCP, RTU, ASCII). This layer communicates
      via the standard asynOctet interface to both the overlying Modbus driver (layer
      2) and to the underlying asyn hardware port driver (layer 4).</li>
    <li>An asyn port driver that handles the low-level communication (TCP/IP or serial).
      This is one of the standard port drivers provided with asyn, i.e. <a href="http://www.aps.anl.gov/epics/modules/soft/asyn/R4-7/asynDriver.html#drvAsynIPPort">
        drvAsynIPPort</a> or <a href="http://www.aps.anl.gov/epics/modules/soft/asyn/R4-7/asynDriver.html#drvAsynSerialPort">
          drvAsynSerialPort</a>. They are not part of the <b>modbus</b> module.</li>
  </ol>
  <p>
    Because <b>modbus</b> makes extensive use of existing asyn facilities, and only
    needs to implement layers 2 and 3 above, the amount of code in <b>modbus</b> is
    quite small (fewer than 2,500 lines).
  </p>
  <p>
    Each <b>modbus</b> port driver is assigned a single Modbus function code and a single
    contiguous range of Modbus memory, up to 2000 bits or 125 words. One typically creates
    several <b>modbus</b> port drivers for a single PLC, each driver reading or writing
    a different set of discrete inputs, coils, input registers or holding registers.
    For example, one might create one port driver to read discrete inputs X0-X37, a
    second to read control registers C0-C377, and a third to write control registers
    C300-C377.
  </p>
  <p>
    The behavior of the port driver differs for read function codes (1, 2, 3, 4) and
    write function codes (5, 6, 15, 16).
  </p>
  <h3>
    Modbus read functions</h3>
  <p>
    For read function codes the driver spawns a poller thread. The poller thread reads
    the entire block of Modbus memory assigned to this port in a single Modbus transaction.
    The values are stored in a buffer in the driver. The delay between polls is set
    when the port driver is created, and can be changed later at run-time. The values
    are read by EPICS using the standard asyn interfaces (asynUInt32Digital, asynInt32,
    asynFloat64, etc.) The values that are read are the last stored values from the
    poller thread. This means that EPICS read operations are <i>synchronous</i>, i.e.
    they do not block because no Modbus I/O is required.
  </p>
  <p>
    For read functions it is possible to set the EPICS records to "I/O Intr" scanning.
    If this is done then the port driver will call back device support whenever there
    is new data for that input. This improves efficiency, because such records only
    process when needed, they do not need to be periodically scanned.
  </p>
  <h3>
    Modbus write functions</h3>
  <p>
    For write function codes the driver does not itself create a separate thread. Rather
    the driver does the Modbus I/O immediately in response to the write operations on
    the standard asyn interfaces. This means that EPICS write operations are <i>asynchronous</i>,
    i.e. they do block because Modbus I/O is required. When the <b>modbus</b> driver
    is created it tells asynManager that it can block, and asynManager creates a separate
    thread that executes the write operations.
  </p>
  <p>
    Word write operations using the asynUInt32Digital interface (with a mask parameter
    that is not 0x0 or 0xFFFF) are done using read/modify/write operations. This allows
    multiple Modbus clients to write and read single words in the same block of Modbus
    memory. However, it <i>does not</i> guarantee correct operation if multiple Modbus
    clients (or the PLC itself) can modify bits within a single word. This is because
    the Modbus server cannot perform the read/modify/write I/O as an atomic operation
    at the level of the Modbus client.
  </p>
  <p>
    For write operations it is possible to specify that a single read operation should
    be done when the port driver is created. This is normally used so that EPICS obtains
    the current value of an output device when the IOC is initialized.</p>
  <p>
    Modbus RTU specifies a minimum delay of 3.5 character times between writes to the
    device. The modbusInterposeConfig function allows one to specify a write delay in
    msec before each write.</p>
  <h3>
    Platform independence</h3>
  <p>
    <b>modbus</b> should run on all EPICS platforms. It has been tested on linux-x86,
    vxWorks, win32-x86 (native Windows with Microsoft VS .NET 2003 C++ compiler), and
    cygwin-x86 (Windows with gcc compiler and Cygwin library).
  </p>
  <p>
    The only thing that may be architecture dependent in <b>modbus</b> is the structure
    packing in modbus.h. The "#pragma pack(1)" directive used there is supported on
    gnu and Microsoft compilers. If this directive is not supported on some compilers
    of interest then modbus.h will need to have the appropriate architecture dependent
    code added.
  </p>
  <h2 id="Creating_a_modbus_port_driver">
    Creating a <b>modbus</b> port driver</h2>
  <p>
    Before <b>modbus</b> port drivers can be created, it is necessary to first create
    at least one asyn TCP/IP or serial port driver to communicate with the hardware.
    The commands required depend on the communications link being used.</p>
  <h3>
    TCP/IP</h3>
  <p>
    For TCP/IP use the following standard asyn command:
  </p>
  <pre>drvAsynIPPortConfigure(portName, hostInfo, priority, noAutoConnect, noProcessEos)
</pre>
  <p>
    Documentation on this command can be found in the <a href="http://www.aps.anl.gov/epics/modules/soft/asyn/R4-18/asynDriver.html#drvAsynIPPort">
      asynDriver documentation</a>.
  </p>
  <p>
    The following example creates an asyn IP port driver called "Koyo1" on port 502
    at IP address 164.54.160.158. The default priority is used and the noAutoConnect
    flag is set to 0 so that asynManager will do normal automatic connection management.
    The noProcessEos flag is set to 1 because Modbus over TCP does not require end-of-string
    processing.
  </p>
  <pre>drvAsynIPPortConfigure("Koyo1","164.54.160.158:502",0,0,1)
</pre>
  <h3>
    Serial RTU</h3>
  <p>
    For serial RTU use the following standard asyn commands:
  </p>
  <pre>drvAsynSerialPortConfigure(portName, ttyName, priority, noAutoConnect, noProcessEos)
asynSetOption(portName, addr, key, value)
</pre>
  <p>
    Documentation on these commands can be found in the <a href="http://www.aps.anl.gov/epics/modules/soft/asyn/R4-7/asynDriver.html#drvAsynSerialPort">
      asynDriver documentation</a>.
  </p>
  <p>
    The following example creates an asyn local serial port driver called "Koyo1" on
    /dev/ttyS1. The default priority is used and the noAutoConnect flag is set to 0
    so that asynManager will do normal automatic connection management. The noProcessEos
    flag is set to 0 because Modbus over serial requires end-of-string processing. The
    serial port parameters are configured to 38400 baud, no parity, 8 data bits, 1 stop
    bit.</p>
  <pre>drvAsynSerialPortConfigure("Koyo1", "/dev/ttyS1", 0, 0, 0)
asynSetOption("Koyo1",0,"baud","38400")
asynSetOption("Koyo1",0,"parity","none")
asynSetOption("Koyo1",0,"bits","8")
asynSetOption("Koyo1",0,"stop","1")
</pre>
  <h3>
    Serial ASCII</h3>
  <p>
    For serial ASCII use the same commands described above for serial RTU. After the
    asynSetOption commands use the following standard asyn commands:</p>
  <pre>asynOctetSetOutputEos(portName, addr, eos)
asynOctetSetInputEos(portName, addr, eos)
</pre>
  <p>
    Documentation on these commands can be found in the <a href="http://www.aps.anl.gov/epics/modules/soft/asyn/R4-7/asynDriver.html#DiagnosticAids">
      asynDriver documentation</a>.</p>
  <p>
    The following example creates an asyn local serial port driver called "Koyo1" on
    /dev/ttyS1. The default priority is used and the noAutoConnect flag is set to 0
    so that asynManager will do normal automatic connection management. The noProcessEos
    flag is set to 0 because Modbus over serial requires end-of-string processing. The
    serial port parameters are configured to 38400 baud, no parity, 8 data bits, 1 stop
    bit. The input and output end-of-string is set to CR/LF.
  </p>
  <pre>drvAsynSerialPortConfigure("Koyo1", "/dev/ttyS1", 0, 0, 0)
asynSetOption("Koyo1",0,"baud","38400")
asynSetOption("Koyo1",0,"parity","none")
asynSetOption("Koyo1",0,"bits","8")
asynSetOption("Koyo1",0,"stop","1")
asynOctetSetOutputEos("Koyo1",0,"\r\n")
asynOctetSetInputEos("Koyo1",0,"\r\n")
</pre>
  <h3>
    modbusInterposeConfig</h3>
  <p>
    After creating the asynIPPort or asynSerialPort driver, the next step is to add
    the asyn "interpose interface" driver. This driver takes the device-independent
    Modbus frames and adds or removes the communication-link specific information for
    the TCP, RTU, or ASCII link protocols. The interpose driver is created with the
    command:</p>
  <pre>modbusInterposeConfig(portName, 
                      linkType,
                      timeoutMsec,
                      writeDelayMsec)
</pre>
  <table border="1" cellpadding="2" cellspacing="2" style="text-align: left">
    <tbody>
      <tr>
        <th align="center" colspan="3,">
          modbusInterposeConfig command</th>
      </tr>
      <tr>
        <th>
          Parameter</th>
        <th>
          Data type</th>
        <th>
          Description</th>
      </tr>
      <tr>
        <td>
          portName</td>
        <td>
          string</td>
        <td>
          Name of the asynIPPort or asynSerialPort previously created.</td>
      </tr>
      <tr>
        <td>
          linkType</td>
        <td>
          int</td>
        <td>
          Modbus link layer type:<br />
          0 = TCP/IP<br />
          1 = RTU<br />
          2 = ASCII</td>
      </tr>
      <tr>
        <td>
          timeoutMsec</td>
        <td>
          int</td>
        <td>
          The timeout in milliseconds for write and read operations to the underlying asynOctet
          driver. This value is used in place of the timeout parameter specified in EPICS
          device support. If zero is specified then a default timeout of 2000 milliseconds
          is used.</td>
      </tr>
      <tr>
        <td>
          writeDelayMsec</td>
        <td>
          int</td>
        <td>
          The delay in milliseconds before each write from EPICS to the device. This is typically
          only needed for Serial RTU devices. The Modicon Modbus Protocol Reference Guide
          says this must be at least 3.5 character times, e.g. about 3.5ms at 9600 baud, for
          Serial RTU. The default is 0.</td>
      </tr>
    </tbody>
  </table>
  <p>
    For the serial ASCII example above, after the asynOctetSetInputEos command, the
    following command would be used. This uses a timeout of 1 second, and a write delay
    of 0 ms.</p>
  <pre>modbusInterposeConfig("Koyo1",2,1000,0)
</pre>
  <h3>
    drvModbusAsynConfigure</h3>
  <p>
    Once the asyn IP or serial port driver has been created, and the modbusInterpose
    driver has been configured, a <b>modbus</b> port driver is created with the following
    command:</p>
  <pre>drvModbusAsynConfigure(portName, 
                       tcpPortName,
                       slaveAddress, 
                       modbusFunction, 
                       modbusStartAddress, 
                       modbusLength,
                       dataType,
                       pollMsec, 
                       plcType);
</pre>
  <table border="1" cellpadding="2" cellspacing="2" style="text-align: left">
    <tbody>
      <tr>
        <th align="center" colspan="3,">
          drvModbusAsynConfigure command</th>
      </tr>
      <tr>
        <th>
          Parameter</th>
        <th>
          Data type</th>
        <th>
          Description</th>
      </tr>
      <tr>
        <td>
          portName</td>
        <td>
          string</td>
        <td>
          Name of the <b>modbus</b> port to be created.</td>
      </tr>
      <tr>
        <td>
          tcpPortName</td>
        <td>
          string</td>
        <td>
          Name of the asyn IP or serial port previously created.</td>
      </tr>
      <tr>
        <td>
          slaveAddress</td>
        <td>
          int</td>
        <td>
          The address of the Modbus slave. This must match the configuration of the Modbus
          slave (PLC) for RTU and ASCII. For TCP the slave address is used for the "unit identifier",
          the last field in the MBAP header. The "unit identifier" is ignored by most PLCs,
          but may be required by some.</td>
      </tr>
      <tr>
        <td>
          modbusFunction</td>
        <td>
          int</td>
        <td>
          Modbus function code (1, 2, 3, 4, 5, 6, 15, or 16).</td>
      </tr>
      <tr>
        <td>
          modbusStartAddress</td>
        <td>
          int</td>
        <td>
          Start address for the Modbus data segment to be accessed.<br />
          (0-65535 decimal, 0-0177777 octal).</td>
      </tr>
      <tr>
        <td>
          modbusLength</td>
        <td>
          int</td>
        <td>
          The length of the Modbus data segment to be accessed.<br />
          This is specified in bits for Modbus functions 1, 2, 5 and 15.<br />
          It is specified in 16-bit words for Modbus functions 3, 4, 6 and 16.<br />
          Length limit is 2000 for functions 1 and 2, 1968 for functions 5 and 15,<br />
          125 for functions 3 and 4, and 123 for functions 6 and 16. </td>
      </tr>
      <tr>
        <td>
          modbusDataType</td>
        <td>
          int</td>
        <td>
          This sets the default data type for this port. This is the data type used if the
          drvUser field of a record is empty, or if it is MODBUS_DATA. The supported Modbus
          data types and correponding drvUser fields are described in the table below.</td>
      </tr>
      <tr>
        <td>
          pollMsec</td>
        <td>
          int</td>
        <td>
          Polling delay time in msec for the polling thread for read functions.<br />
          For write functions, a non-zero value means that the Modbus data should<br />
          be read once when the port driver is first created.</td>
      </tr>
      <tr>
        <td>
          plcType</td>
        <td>
          string</td>
        <td>
          Type of PLC (e.g. Koyo, Modicon, etc.).
          <br />
          This parameter is currently used to print information in asynReport.<br />
          It is also used to treat Wago devices specially if the plcType string contains the
          substring "Wago". See the note below. </td>
      </tr>
    </tbody>
  </table>
  <h3>
    Modbus register data types
  </h3>
  <p>
    Modbus function codes 3, 4, 6, and 16 are used to access 16-bit registers. The Modbus
    specification does not define how the data in these registers is to be interpreted,
    for example as signed or unsigned numbers, binary coded decimal (BCD) values, etc.
    In fact many manufacturers combine multiple 16-bit registers to encode 32-bit integers,
    32-bit or 64-bit floats, etc. The following table lists the data types supported
    by <b>modbus</b>. The default data type for the port is defined with the modbusDataType
    parameter described above. The data type for particular record can override the
    default by specifying a different data type with the drvUser field in the link.
    The driver uses this information to convert the number between EPICS device support
    and Modbus. Data is transferred to and from EPICS device support as epicsUInt32,
    epicsInt32, and epicsFloat64 numbers. Note that the data type conversions described
    in this table only apply for records using the asynInt32 or asynFloat64 interfaces,
    they do not apply when using the asynUInt32Digital interface. The asynUInt32Digital
    interface always treats the registers as unsigned 16-bit integers.
  </p>
  <table border="1" cellpadding="2" cellspacing="2" style="text-align: left">
    <tbody>
      <tr>
        <th align="center" colspan="3">
          Supported Modbus data types</th>
      </tr>
      <tr>
        <th>
          modbusDataType value</th>
        <th>
          drvUser field</th>
        <th>
          Description</th>
      </tr>
      <tr>
        <td>
          0</td>
        <td>
          UINT16</td>
        <td>
          Unsigned 16-bit binary integers</td>
      </tr>
      <tr>
        <td>
          1</td>
        <td>
          INT16SM</td>
        <td>
          16-bit binary integers, sign and magnitude format. In this format bit 15 is the
          sign bit, and bits 0-14 are the absolute value of the magnitude of the number. This
          is one of the formats used, for example, by Koyo PLCs for numbers such as ADC conversions.
        </td>
      </tr>
      <tr>
        <td>
          2</td>
        <td>
          BCD_UNSIGNED</td>
        <td>
          Binary coded decimal (BCD), unsigned. This data type is for a 16-bit number consisting
          of 4 4-bit nibbles, each of which encodes a decimal number from 0-9. A BCD number
          can thus store numbers from 0 to 9999. Many PLCs store some numbers in BCD format.
        </td>
      </tr>
      <tr>
        <td>
          3</td>
        <td>
          BCD_SIGNED</td>
        <td>
          4-digit binary coded decimal (BCD), signed. This data type is for a 16-bit number
          consisting of 3 4-bit nibbles, and one 3-bit nibble. Bit 15 is a sign bit. Signed
          BCD numbers can hold values from -7999 to +7999. This is one of the formats used
          by Koyo PLCs for numbers such as ADC conversions.</td>
      </tr>
      <tr>
        <td>
          4</td>
        <td>
          INT16</td>
        <td>
          16-bit signed (2's complement) integers. This data type extends the sign bit when
          converting to epicsInt32.</td>
      </tr>
      <tr>
        <td>
          5</td>
        <td>
          INT32_LE</td>
        <td>
          32-bit integers, little endian (least significant word at Modbus address N, most
          significant word at Modbus address N+1)</td>
      </tr>
      <tr>
        <td>
          6</td>
        <td>
          INT32_BE</td>
        <td>
          32-bit integers, big endian (most significant word at Modbus address N, least significant
          word at Modbus address N+1)</td>
      </tr>
      <tr>
        <td>
          7</td>
        <td>
          FLOAT32_LE</td>
        <td>
          32-bit floating point, little endian (least significant word at Modbus address N,
          most significant word at Modbus address N+1)</td>
      </tr>
      <tr>
        <td>
          8</td>
        <td>
          FLOAT32_BE</td>
        <td>
          32-bit floating point, big endian (most significant word at Modbus address N, least
          significant word at Modbus address N+1)</td>
      </tr>
      <tr>
        <td>
          9</td>
        <td>
          FLOAT64_LE</td>
        <td>
          64-bit floating point, little endian (least significant word at Modbus address N,
          most significant word at Modbus address N+3)</td>
      </tr>
      <tr>
        <td>
          10</td>
        <td>
          FLOAT64_BE</td>
        <td>
          64-bit floating point, big endian (most significant word at Modbus address N, least
          significant word at Modbus address N+3)</td>
      </tr>
    </tbody>
  </table>
  <p>
    Note that if it is desired to transmit BCD numbers untranslated to EPICS over the
    asynInt32 interface, then data type 0 should be used, because no translation is
    done in this case.</p>
  <p>
    The following is an example ai record using 32-bit floating point values:</p>
  <pre>
# ai record template for register inputs
record(ai, "$(P)$(R)") {
    field(DTYP,"asynFloat64")
    field(INP,"@asyn($(PORT) $(OFFSET))FLOAT32_LE")
    field(HOPR,"$(HOPR)")
    field(LOPR,"$(LOPR)")
    field(PREC,"$(PREC)")
    field(SCAN,"$(SCAN)")
}   
</pre>
  <h3>
    Note for Wago devices</h3>
  <p>
    This initial read operation is normally done at the same Modbus address as the write
    operations. However, Wago devices are different from other Modbus devices because
    the address to read back a register is not the same as the address to write the
    register. For Wago devices the address used to read back the initial value for a
    Modbus write function must be 0x200 greater than the address for the write function.
    This is handled by adding this 0x200 offset for the readback address if the plcType
    argument to drvModbusAsynConfigure contains the substring "Wago" (case sensitive).
    Note that this does not affect the address for Wago read functions. The user must
    specify the actual Modbus address for read functions.</p>
  <h3>
    Number of drvAsynIPPort drivers for TCP
  </h3>
  <p>
    Each drvAsynIPPort driver creates a separate TCP/IP socket connection to the PLC.
    It is possible to have all of the <b>modbus</b> port drivers share a single drvAsynIPPort
    driver. In this case all I/O to the PLC is done over a single socket in a "serial"
    fashion. A transaction for one <b>modbus</b> driver must complete before a transaction
    for another <b>modbus</b> driver can begin. It is also possible to create multiple
    drvAsynIPPort drivers (sockets) to a single PLC and, for example, use a different
    drvAsynIPPort for each <b>modbus</b> port. In this case I/O operations from multiple
    <b>modbus</b> drivers can proceed in parallel, rather than serially. This could
    improve performance at the expense of more CPU load on the IOC and PLC, and more
    network traffic.</p>
  <p>
    It is important to note, however, that many PLCs will time out sockets after a few
    seconds of inactivity. This is not a problem with <b>modbus</b> drivers that use
    read function codes, because they are polling frequently. But <b>modbus</b> drivers
    that use write function codes may only do occasional I/O, and hence may time out
    if they are the only ones communicating through a drvAsynIPPort driver. Thus, it
    is usually necessary for <b>modbus</b> drivers with write function codes to use
    the same drvAsynIPPort driver (socket) as at least one <b>modbus</b> driver with
    a read function code to avoid timeouts.</p>
  <p>
    The choice of how many drvAsynIPPort drivers to use per PLC will be based on empirical
    performance versus resource usage considerations. In general it is probably a good
    idea to start with one drvAsynIPPort server per PLC (e.g. shared by all <b>modbus</b>
    drivers for that PLC) and see if this results in satisfactory performance.</p>
  <h3>
    Number formats</h3>
  <p>
    It can be convenient to specify the modbusStartAddress and modbusLength in octal,
    rather than decimal, because this is the convention on most PLCs. In the iocsh and
    vxWorks shells this is done by using a leading 0 on the number, i.e. 040400 is an
    octal number.</p>
  <h2 id="EPICS_device_support">
    EPICS device support</h2>
  <p>
    <b>modbus</b> implements the following standard asyn interfaces:
  </p>
  <ul>
    <li>asynUInt32Digital</li>
    <li>asynInt32</li>
    <li>asynInt32Array</li>
    <li>asynFloat64</li>
    <li>asynCommon</li>
    <li>asynDrvUser</li>
  </ul>
  <p>
    Because it implements these standard interfaces, EPICS device support is done entirely
    with the generic EPICS device support provided with asyn itself. There is no special
    device support provided as part of <b>modbus</b>.</p>
  <p>
    It is necessary to use asyn R4-8 or later, because some minor enhancements were
    made to asyn to support the features required by <b>modbus</b>.
  </p>
  <p>
    The following tables document the asyn interfaces used by the EPICS device support.
  </p>
  <p>
    The <b>drvUser</b> parameter is used by the driver to determine what command is
    being sent from device support. The default is MODBUS_DATA, which is thus optional
    in the link specification in device support. If no <b>drvUser</b> field is specified,
    or if MODBUS_DATA is specified, then the Modbus data type for records using the
    asynInt32 and asynFloat64 interfaces is the default data type specified in the drvModbusAsynConfigure
    command. Records can override the default Modbus data type by specifying datatype-specific
    <b>drvUser</b> field, e.g. BCD_SIGNED, INT16, FLOAT32_LE, etc.</p>
  <p>
    The <b>offset</b> parameter is used to specify the location of the data for a record
    relative to the starting Modbus address for that driver. This <b>offset</b> is specified
    in bits for drivers using Modbus functions 1, 2, 5, and 15 that control discrete
    inputs or coils. For example, if the Modbus function is 2 and the Modbus starting
    address is 04000, then <b>offset=2</b> refers to address 04002. For a Koyo PLC the
    X inputs are at this Modbus starting address for Modbus function 2, so <b>offset=2</b>
    is input X2.</p>
  <p>
    The <b>offset</b> is specified in words for drivers using Modbus functions 3, 4,
    6 and 16 that address input registers or holding registers. For example, if the
    Modbus function is set to 6 and the Modbus address is 040600 then <b>offset=2</b>
    refers to address 040602. For a Koyo PLC the C control relays are accessed as 16-bit
    words at this Modbus starting address for Modbus function 6. <b>offset=2</b> will
    thus write to the third 16 bit-word, which is coils C40-C57.</p>
  <p>
    For 32-bit or 64-bit data types (INT32_LE, INT32_BE, FLOAT32_LE, FLOAT32_BE) the
    <b>offset</b> specifies the location of the first 16-bit register, and the second
    register is at <b>offset+1</b>, etc.</p>
  <h3>
    asynUInt32Digital</h3>
  <p>
    asynUInt32Digital device support is selected with
  </p>
  <pre>field(DTYP,"asynUInt32Digital")
field(INP,"@asynMask(portName,offset,mask,timeout)drvUser")
    </pre>
  <table border="1" cellpadding="2" cellspacing="2" style="text-align: left">
    <tbody>
      <tr>
        <th align="center" colspan="6,">
          asynUInt32Digital Device Support</th>
      </tr>
      <tr>
        <th>
          Modbus function</th>
        <th>
          Offset type</th>
        <th>
          Data type</th>
        <th>
          drvUser</th>
        <th>
          Records supported</th>
        <th>
          Description</th>
      </tr>
      <tr>
        <td>
          1, 2</td>
        <td>
          Bit</td>
        <td>
          Single bit</td>
        <td>
          MODBUS_DATA</td>
        <td>
          bi, mbbi, mbbiDirect, longin</td>
        <td>
          value = (Modbus data &amp; mask)<br />
          (normally mask=1)</td>
      </tr>
      <tr>
        <td>
          3, 4</td>
        <td>
          16-bit word</td>
        <td>
          16-bit word</td>
        <td>
          MODBUS_DATA</td>
        <td>
          bi, mbbi, mbbiDirect, longin</td>
        <td>
          value = (Modbus data &amp; mask)<br />
          (mask selects bits of interest)</td>
      </tr>
      <tr>
        <td>
          5</td>
        <td>
          Bit</td>
        <td>
          Single bit</td>
        <td>
          MODBUS_DATA</td>
        <td>
          bo, mbbo, mbboDirect, longout</td>
        <td>
          Modbus write (value &amp; mask)<br />
          (normally mask=1)</td>
      </tr>
      <tr>
        <td>
          6</td>
        <td>
          16-bit word</td>
        <td>
          16-bit word</td>
        <td>
          MODBUS_DATA</td>
        <td>
          bo, mbbo, mbboDirect, longout</td>
        <td>
          If mask==0 or mask==0xFFFF does Modbus write (value)<br />
          Else does read/modify/write:<br />
          Sets bits that are set in value and set in mask,<br />
          Clears bits that are clear in value and set in mask</td>
      </tr>
      <tr>
        <td>
          Any</td>
        <td>
          NA</td>
        <td>
          NA</td>
        <td>
          ENABLE_HISTOGRAM</td>
        <td>
          bi, mbbi, mbbiDirect, longin</td>
        <td>
          Returns 0/1 if I/O time histogramming is disabled/enabled in driver<br />
        </td>
      </tr>
      <tr>
        <td>
          Any</td>
        <td>
          NA</td>
        <td>
          NA</td>
        <td>
          ENABLE_HISTOGRAM</td>
        <td>
          bo, mbbo, mbboDirect, longout</td>
        <td>
          If value = 0/1 then disable/enable I/O time histogramming in driver<br />
        </td>
      </tr>
    </tbody>
  </table>
  <h3>
    asynInt32</h3>
  <p>
    asynInt32 device support is selected with
  </p>
  <pre>field(DTYP,"asynInt32")
field(INP,"@asyn(portName,offset,timeout)drvUser")
    </pre>
  <p>
    or</p>
  <pre>field(INP,"@asynMask(portName,offset,nbits,timeout)drvUser")
    </pre>
  <p>
    The asynMask syntax is used for analog I/O devices, in order to specify the number
    of bits in the device. This is required for Modbus because the driver only knows
    that it is returning a 16-bit register, but not the actual number of bits in the
    device, and hence cannot return meaningful data with asynInt32->getBounds().
  </p>
  <p>
    nbits>0 for a unipolar device. For example, nbits=12 means unipolar 12-bit device,
    with a range of 0 to 4095. nbits&lt;0 for a bipolar device. For example, nbits=-12
    means bipolar 12-bit device, with a range of -2048 to 2047)</p>
  <p>
    Note: when writing 32-bit or 64-bit values function code 16 should be used if the
    device supports it. The write will then be "atomic". If function code 6 is used
    then the data will be written in multiple messages, and there will be an short time
    period in which the device has incorrect data.
  </p>
  <table border="1" cellpadding="2" cellspacing="2" style="text-align: left">
    <tbody>
      <tr>
        <th align="center" colspan="6,">
          asynInt32 Device Support</th>
      </tr>
      <tr>
        <th>
          Modbus function</th>
        <th>
          Offset type</th>
        <th>
          Data type</th>
        <th>
          drvUser</th>
        <th>
          Records supported</th>
        <th>
          Description</th>
      </tr>
      <tr>
        <td>
          1, 2</td>
        <td>
          Bit</td>
        <td>
          Single bit</td>
        <td>
          MODBUS_DATA</td>
        <td>
          ai, mbbi, longin</td>
        <td>
          value = (epicsUInt32)Modbus data</td>
      </tr>
      <tr>
        <td>
          3, 4</td>
        <td>
          16-bit words</td>
        <td>
          16, 32, or 64-bit word</td>
        <td>
          MODBUS_DATA (or datatype-specific value)</td>
        <td>
          ai, mbbi, longin</td>
        <td>
          value = (epicsInt32)Modbus data</td>
      </tr>
      <tr>
        <td>
          5</td>
        <td>
          Bit</td>
        <td>
          Single bit</td>
        <td>
          MODBUS_DATA</td>
        <td>
          ao, mbbo, longout</td>
        <td>
          Modbus write value</td>
      </tr>
      <tr>
        <td>
          6,16</td>
        <td>
          16-bit words</td>
        <td>
          16, 32, or 64-bit word</td>
        <td>
          MODBUS_DATA (or datatype-specific value)</td>
        <td>
          ao, mbbo, longout</td>
        <td>
          Modbus write value</td>
      </tr>
      <tr>
        <td>
          Any</td>
        <td>
          NA</td>
        <td>
          NA</td>
        <td>
          READ_OK</td>
        <td>
          ai, mbbi, longin</td>
        <td>
          Returns number of successful read operations on this asyn port</td>
      </tr>
      <tr>
        <td>
          Any</td>
        <td>
          NA</td>
        <td>
          NA</td>
        <td>
          WRITE_OK</td>
        <td>
          ai, mbbi, longin</td>
        <td>
          Returns number of successful write operations on this asyn port</td>
      </tr>
      <tr>
        <td>
          Any</td>
        <td>
          NA</td>
        <td>
          NA</td>
        <td>
          IO_ERRORS</td>
        <td>
          ai, mbbi, longin</td>
        <td>
          Returns number of I/O errors on this asyn port</td>
      </tr>
      <tr>
        <td>
          Any</td>
        <td>
          NA</td>
        <td>
          NA</td>
        <td>
          LAST_IO_TIME</td>
        <td>
          ai, mbbi, longin</td>
        <td>
          Returns number of milliseconds for last I/O operation</td>
      </tr>
      <tr>
        <td>
          Any</td>
        <td>
          NA</td>
        <td>
          NA</td>
        <td>
          MAX_IO_TIME</td>
        <td>
          ai, mbbi, longin</td>
        <td>
          Returns maximum number of milliseconds for I/O operations</td>
      </tr>
    </tbody>
  </table>
  <h3>
    asynFloat64</h3>
  <p>
    asynFloat64 device support is selected with</p>
  <pre>field(DTYP,"asynFloat64")
field(INP,"@asyn(portName,offset,timeout)drvUser")</pre>
  <p>
    Note: when writing 32-bit or 64-bit values function code 16 should be used if the
    device supports it. The write will then be "atomic". If function code 6 is used
    then the data will be written in multiple messages, and there will be an short time
    period in which the device has incorrect data.
  </p>
  <table border="1" cellpadding="2" cellspacing="2" style="text-align: left">
    <tbody>
      <tr>
        <th align="center" colspan="6,">
          asynFloat64 Device Support</th>
      </tr>
      <tr>
        <th>
          Modbus function</th>
        <th>
          Offset type</th>
        <th>
          Data type</th>
        <th>
          drvUser</th>
        <th>
          Records supported</th>
        <th>
          Description</th>
      </tr>
      <tr>
        <td>
          1, 2</td>
        <td>
          Bit</td>
        <td>
          Single bit</td>
        <td>
          MODBUS_DATA</td>
        <td>
          ai</td>
        <td>
          value = (epicsFloat64)Modbus data</td>
      </tr>
      <tr>
        <td>
          3, 4</td>
        <td>
          16-bit words</td>
        <td>
          16, 32, or 64-bit word</td>
        <td>
          MODBUS_DATA (or datatype-specific value)</td>
        <td>
          ai</td>
        <td>
          value = (epicsFloat64)Modbus data</td>
      </tr>
      <tr>
        <td>
          5</td>
        <td>
          Bit</td>
        <td>
          Single bit</td>
        <td>
          MODBUS_DATA</td>
        <td>
          ao</td>
        <td>
          Modbus write (epicsUInt16)value</td>
      </tr>
      <tr>
        <td>
          6, 16</td>
        <td>
          16-bit word</td>
        <td>
          16-bit word</td>
        <td>
          MODBUS_DATA (or datatype-specific value)</td>
        <td>
          ao</td>
        <td>
          Modbus write value</td>
      </tr>
      <tr>
        <td>
          Any</td>
        <td>
          NA</td>
        <td>
          NA</td>
        <td>
          POLL_DELAY</td>
        <td>
          ai, ao</td>
        <td>
          Read or write the delay time in seconds between polls for the read poller thread.
        </td>
      </tr>
    </tbody>
  </table>
  <h3>
    asynInt32Array</h3>
  <p>
    asynInt32Array device support is selected with</p>
  <pre>field(DTYP,"asynInt32ArrayIn")
field(INP,"@asyn(portName,offset,timeout)drvUser")
    </pre>
  <p>
    or</p>
  <pre>field(DTYP,"asynInt32ArrayOut")
field(INP,"@asyn(portName,offset,timeout)drvUser")
    </pre>
  <p>
    asynInt32Array device support is used to read or write arrays of up to 2000 coil
    values or up to 125 16-bit registers. It is also used to read the histogram array
    of I/O times when histogramming is enabled.
  </p>
  <table border="1" cellpadding="2" cellspacing="2" style="text-align: left">
    <tbody>
      <tr>
        <th align="center" colspan="6,">
          asynInt32Array Device Support</th>
      </tr>
      <tr>
        <th>
          Modbus function</th>
        <th>
          Offset type</th>
        <th>
          Data type</th>
        <th>
          drvUser</th>
        <th>
          Records supported</th>
        <th>
          Description</th>
      </tr>
      <tr>
        <td>
          1, 2</td>
        <td>
          NA</td>
        <td>
          Array of bits</td>
        <td>
          MODBUS_DATA</td>
        <td>
          waveform (input)</td>
        <td>
          value = (epicsInt32)Modbus data[]</td>
      </tr>
      <tr>
        <td>
          3, 4</td>
        <td>
          NA</td>
        <td>
          Array of 16, 32 or 64-bit words</td>
        <td>
          MODBUS_DATA (or datatype-specific value)</td>
        <td>
          waveform (input)</td>
        <td>
          value = (epicsInt32)Modbus data[]</td>
      </tr>
      <tr>
        <td>
          15</td>
        <td>
          NA</td>
        <td>
          Array of bits</td>
        <td>
          MODBUS_DATA</td>
        <td>
          waveform (output)</td>
        <td>
          Modbus write (epicsUInt16)value[]</td>
      </tr>
      <tr>
        <td>
          16</td>
        <td>
          NA</td>
        <td>
          Array of 16, 32, or 64-bit words</td>
        <td>
          MODBUS_DATA (or datatype-specific value)</td>
        <td>
          waveform (output)</td>
        <td>
          Modbus write value[]</td>
      </tr>
      <tr>
        <td>
          Any</td>
        <td>
          NA</td>
        <td>
          NA</td>
        <td>
          READ_HISTOGRAM</td>
        <td>
          waveform (input)</td>
        <td>
          Returns a histogram array of the I/O times in milliseconds since histogramming was
          last enabled.</td>
      </tr>
    </tbody>
  </table>
  <h3>
    Template files</h3>
  <p>
    <b>modbus</b> provides example template files in the modbusApp/Db directory. These
    include:</p>
  <table border="1" cellpadding="2" cellspacing="2" style="text-align: left">
    <tbody>
      <tr>
        <th align="center" colspan="3,">
          Template Files</th>
      </tr>
      <tr>
        <th>
          Files</th>
        <th>
          Description</th>
        <th>
          Macro arguments</th>
      </tr>
      <tr>
        <td>
          bi_bit.template</td>
        <td>
          asynUInt32Digital support for bi record with discrete inputs or coils. Mask=1.
        </td>
        <td>
          P, R, PORT, OFFSET, ZNAM, ONAM, ZSV, OSV, SCAN</td>
      </tr>
      <tr>
        <td>
          bi_word.template</td>
        <td>
          asynUInt32Digital support for bi record with register inputs.</td>
        <td>
          P, R, PORT, OFFSET, MASK, ZNAM, ONAM, ZSV, OSV, SCAN</td>
      </tr>
      <tr>
        <td>
          mbbiDirect.template</td>
        <td>
          asynUInt32Digital support for mbbiDirect record with register inputs.</td>
        <td>
          P, R, PORT, OFFSET, MASK, SCAN</td>
      </tr>
      <tr>
        <td>
          longin.template</td>
        <td>
          asynUInt32Digital support for longin record with register inputs. Mask=0xFFFF.
        </td>
        <td>
          P, R, PORT, OFFSET, SCAN</td>
      </tr>
      <tr>
        <td>
          longinInt32.template</td>
        <td>
          asynInt32 support for longin record with register inputs. </td>
        <td>
          P, R, PORT, OFFSET, SCAN, DATA_TYPE</td>
      </tr>
      <tr>
        <td>
          intarray_in.template</td>
        <td>
          asynInt32Array support for waveform record with discrete, coil, or register inputs.
        </td>
        <td>
          P, R, PORT, NELM, SCAN</td>
      </tr>
      <tr>
        <td>
          bo_bit.template</td>
        <td>
          asynUInt32Digital support for bo record with coil outputs. Mask=1.</td>
        <td>
          P, R, PORT, OFFSET, ZNAM, ONAM</td>
      </tr>
      <tr>
        <td>
          bo_word.template</td>
        <td>
          asynUInt32Digital support for bo record with register outputs.</td>
        <td>
          P, R, PORT, OFFSET, MASK, ZNAM, ONAM</td>
      </tr>
      <tr>
        <td>
          mbboDirect.template</td>
        <td>
          asynUInt32Digital support for mbboDirect record with register outputs.</td>
        <td>
          P, R, PORT, OFFSET, MASK</td>
      </tr>
      <tr>
        <td>
          longout.template</td>
        <td>
          asynUInt32Digital support for longout record with register outputs. Mask=0xFFFF.
        </td>
        <td>
          P, R, PORT, OFFSET</td>
      </tr>
      <tr>
        <td>
          longoutInt32.template</td>
        <td>
          asynInt32 support for longout record with register outputs. </td>
        <td>
          P, R, PORT, OFFSET, DATA_TYPE</td>
      </tr>
      <tr>
        <td>
          intarray_out.template</td>
        <td>
          asynInt32Array support for waveform record with discrete, coil, or register outputs.
        </td>
        <td>
          P, R, PORT, NELM</td>
      </tr>
      <tr>
        <td>
          ai.template</td>
        <td>
          asynInt32 support for ai record with LINEAR conversion</td>
        <td>
          P, R, PORT, OFFSET, BITS, EGUL, EGUF, PREC, SCAN</td>
      </tr>
      <tr>
        <td>
          aiFloat64.template</td>
        <td>
          asynFloat64 support for ai record</td>
        <td>
          P, R, PORT, OFFSET, LOPR, HOPR, PREC, SCAN, DATA_TYPE</td>
      </tr>
      <tr>
        <td>
          ai_average.template</td>
        <td>
          asynInt32Average support for ai record with LINEAR conversion. This support gets
          callbacks each time the poll thread reads the analog input, and averages readings
          until the record is processed.</td>
        <td>
          P, R, PORT, OFFSET, BITS, EGUL, EGUF, PREC, SCAN</td>
      </tr>
      <tr>
        <td>
          ao.template</td>
        <td>
          asynInt32 support for ao record with LINEAR conversion</td>
        <td>
          P, R, PORT, OFFSET, BITS, EGUL, EGUF, PREC</td>
      </tr>
      <tr>
        <td>
          aoFloat64.template</td>
        <td>
          asynFloat64 support for ao record</td>
        <td>
          P, R, PORT, OFFSET, LOPR, HOPR, PREC, DATA_TYPE</td>
      </tr>
      <tr>
        <td>
          asynRecord.template</td>
        <td>
          Support for asyn record. Useful for controlling trace printing, and for debugging.
        </td>
        <td>
          P, R, PORT, ADDR, TMOD, IFACE</td>
      </tr>
      <tr>
        <td>
          poll_delay.template</td>
        <td>
          Support for ao record to control the delay time for the poller thread.</td>
        <td>
          P, R, PORT</td>
      </tr>
      <tr>
        <td>
          statistics.template</td>
        <td>
          Support for bo, longin and waveform records to read I/O statistics for the port.
        </td>
        <td>
          P, R, PORT, SCAN</td>
      </tr>
    </tbody>
  </table>
  <p>
    The following table explains the macro parameters used in the preceeding table.
  </p>
  <table border="1" cellpadding="2" cellspacing="2" style="text-align: left">
    <tbody>
      <tr>
        <th align="center" colspan="2,">
          Macro Parameters</th>
      </tr>
      <tr>
        <th>
          Macro</th>
        <th>
          Description</th>
      </tr>
      <tr>
        <td>
          P</td>
        <td>
          Prefix for record name. Complete record name is $(P)$(R).</td>
      </tr>
      <tr>
        <td>
          R</td>
        <td>
          Record name. Complete record name is $(P)$(R).</td>
      </tr>
      <tr>
        <td>
          PORT</td>
        <td>
          Port name for <b>modbus</b> asyn port.</td>
      </tr>
      <tr>
        <td>
          OFFSET</td>
        <td>
          Offset for Modbus data relative to start address for this port.</td>
      </tr>
      <tr>
        <td>
          MASK</td>
        <td>
          Bit mask used to select data for this record.</td>
      </tr>
      <tr>
        <td>
          ZNAM</td>
        <td>
          String for 0 value for bi/bo records.</td>
      </tr>
      <tr>
        <td>
          ONAM</td>
        <td>
          String for 1 value for bi/bo records.</td>
      </tr>
      <tr>
        <td>
          ZSV</td>
        <td>
          0 severity for bi/bo records.</td>
      </tr>
      <tr>
        <td>
          OSV</td>
        <td>
          1 severity for bi/bo records.</td>
      </tr>
      <tr>
        <td>
          BITS</td>
        <td>
          Number of bits for analog I/O devices. &gt;0=unipolar, &lt;0=bipolar.</td>
      </tr>
      <tr>
        <td>
          DATA_TYPE</td>
        <td>
          drvUser field specifying the Modbus data type. If this field is blank or is MODBUS_DATA
          then the default datatype specified in the drvModbusAsynConfigure command is used.
          Other allowed values are listed in the table above (UINT16, INT16SM, BCD_SIGNED,
          etc.)</td>
      </tr>
      <tr>
        <td>
          EGUL</td>
        <td>
          Engineering value for lower limit of analog device.</td>
      </tr>
      <tr>
        <td>
          EGUF</td>
        <td>
          Engineering value for upper limit of analog device.</td>
      </tr>
      <tr>
        <td>
          LOPR</td>
        <td>
          Lower display limit of analog device.</td>
      </tr>
      <tr>
        <td>
          HOPR</td>
        <td>
          Upper display limit of analog device.</td>
      </tr>
      <tr>
        <td>
          PREC</td>
        <td>
          Number of digits of precision for ai/ao records.</td>
      </tr>
      <tr>
        <td>
          NELM</td>
        <td>
          Number of elements in waveform records.</td>
      </tr>
      <tr>
        <td>
          ADDR</td>
        <td>
          Address for asyn record, same as OFFSET above.</td>
      </tr>
      <tr>
        <td>
          TMOD</td>
        <td>
          Transfer mode for asyn record.</td>
      </tr>
      <tr>
        <td>
          IFACE</td>
        <td>
          asyn interface for asyn record.</td>
      </tr>
      <tr>
        <td>
          SCAN</td>
        <td>
          Scan rate for record (e.g. "1 second", "I/O Intr", etc.).</td>
      </tr>
    </tbody>
  </table>
  <h2 id="Example_Applications">
    Example Applications</h2>
  <p>
    <b>modbus</b> builds an example application called modbusApp. This application can
    be run to control any number of Modbus PLCs.</p>
  <p>
    In the iocBoot/iocTest directory there are several startup scripts for EPICS IOCs.
    These are designed to test most of the features of the <b>modbus</b> driver on Koyo
    PLCs, such as the DL series from Automation Direct.
  </p>
  <ul>
    <li>Koyo1.cmd creates <b>modbus</b> port drivers to read the X inputs, write to the
      Y outputs, and read and write from the C control registers. Each of these sets of
      inputs and outputs is accessed both as coils and as registers (V memory). bi/bo,
      mbbiDirect/mbboDirect, and waveform records are loaded to read and write using these
      drivers.</li>
    <li>Koyo2.cmd creates <b>modbus</b> port drivers to read the X inputs, write to the
      Y outputs, and read and write from the C control registers. Only coil access is
      used. This example also reads a 4-channel 13-bit bipolar A/D converter. This has
      been tested using both signed-BCD and sign plus magnitude binary formats. Note that
      a ladder logic program must be loaded that does the appropriate conversion of the
      A/D values into V memory.</li>
    <li>st.cmd is a simple example startup script to be run on non-vxWorks IOCs. It just
      loads Koyo1.cmd and Koyo2.cmd. It is invoked using a command like:
      <pre>      ../../bin/linux-x86/modbusApp st.cmd
      </pre>
      One can also load Koyo1.cmd or Koyo2.cmd separately as in:
      <pre>      ../../bin/linux-x86/modbusApp Koyo1.cmd
      </pre>
      st.cmd.vxWorks is a simple example startup script to be run on vxWorks IOCs. It
      just loads Koyo1.cmd and Koyo2.cmd.</li>
  </ul>
  <p>
    The following is the beginning of Koyo1.cmd when it is configured for serial RTU
    with slave address 1 on /dev/ttyS1. It also shows how to configure TCP and serial
    ASCII connections. (Koyo PLCs do not support ASCII however).</p>
  <pre># Koyo1.cmd
&lt; envPaths

dbLoadDatabase("../../dbd/modbus.dbd")
modbus_registerRecordDeviceDriver(pdbbase)

# Use the following commands for TCP/IP
#drvAsynIPPortConfigure(const char *portName, 
#                       const char *hostInfo,
#                       unsigned int priority, 
#                       int noAutoConnect,
#                       int noProcessEos);
#drvAsynIPPortConfigure("Koyo1","164.54.160.200:502",0,1,1)
#modbusInterposeConfig(const char *portName, 
#                      modbusLinkType linkType,
#                      int timeoutMsec)
#modbusInterposeConfig("Koyo1",0,1000)

# Use the following commands for serial RTU or ASCII
#drvAsynSerialPortConfigure(const char *portName, 
#                           const char *ttyName,
#                           unsigned int priority, 
#                           int noAutoConnect,
#                           int noProcessEos);
drvAsynSerialPortConfigure("Koyo1", "/dev/ttyS1", 0, 0, 0)
asynSetOption("Koyo1",0,"baud","38400")
asynSetOption("Koyo1",0,"parity","none")
asynSetOption("Koyo1",0,"bits","8")
asynSetOption("Koyo1",0,"stop","1")

# Use the following command for serial RTU
modbusInterposeConfig("Koyo1",1,1000)

# Use the following commands for serial ASCII
#asynOctetSetOutputEos("Koyo1",0,"\r\n")
#asynOctetSetInputEos("Koyo1",0,"\r\n")
#modbusInterposeConfig("Koyo1",2,1000)

# Trace flags for asynOctet driver
asynSetTraceIOMask("Koyo1",0,4)   # Enable traceIOHex
#asynSetTraceMask("Koyo1",0,9)     # Enable traceError and traceIODriver

# NOTE: We use octal numbers for the start address and length (leading zeros)
#       to be consistent with the PLC nomenclature.  This is optional, decimal
#       numbers (no leading zero) or hex numbers can also be used.
# These commands configure the driver for slave address 1 (the number after "Koyo1").
# For other slave address this number must be changed.  It is typically ignored and
# can be set to 0 for TCP.

# The DL205 has bit access to the Xn inputs at Modbus offset 4000 (octal)
# Read 32 bits (X0-X37).  Function code=2.
drvModbusAsynConfigure("K1_Xn_Bit",      "Koyo1", 1, 2,  04000, 040,    0,  100, "Koyo")

# The DL205 has word access to the Xn inputs at Modbus offset 40400 (octal)
# Read 8 words (128 bits).  Function code=3.
drvModbusAsynConfigure("K1_Xn_Word",     "Koyo1", 1, 3, 040400, 010,    0,  100, "Koyo")

# The DL205 has bit access to the Yn outputs at Modbus offset 4000 (octal)
# Read 32 bits (Y0-Y37).  Function code=1.
drvModbusAsynConfigure("K1_Yn_In_Bit",   "Koyo1", 1, 1,  04000, 040,    0,  100, "Koyo")

# The DL205 has bit access to the Yn outputs at Modbus offset 4000 (octal)
# Write 32 bits (Y0-Y37).  Function code=5.
drvModbusAsynConfigure("K1_Yn_Out_Bit",  "Koyo1", 1, 5,  04000, 040,    0,  1, "Koyo")

# The DL205 has word access to the Yn outputs at Modbus offset 40500 (octal)
# Read 8 words (128 bits).  Function code=3.
drvModbusAsynConfigure("K1_Yn_In_Word",  "Koyo1", 1, 3, 040500, 010,    0,  100, "Koyo")

# Write 8 words (128 bits).  Function code=6.
drvModbusAsynConfigure("K1_Yn_Out_Word", "Koyo1", 1, 6, 040500, 010,    0,  100, "Koyo")

# The DL205 has bit access to the Cn bits at Modbus offset 6000 (octal)
# Access 256 bits (C0-C377) as inputs.  Function code=1.
drvModbusAsynConfigure("K1_Cn_In_Bit",   "Koyo1", 1, 1,  06000, 0400,   0,  100, "Koyo")

# Access the same 256 bits (C0-C377) as outputs.  Function code=5.
drvModbusAsynConfigure("K1_Cn_Out_Bit",  "Koyo1", 1, 5,  06000, 0400,   0,  1,  "Koyo")

# Access the same 256 bits (C0-C377) as array outputs.  Function code=15.
drvModbusAsynConfigure("K1_Cn_Out_Bit_Array",  "Koyo1", 1, 15,  06000, 0400,   0,   1, "Koyo")

# The DL205 has word access to the Cn bits at Modbus offset 40600 (octal)
# We use the first 16 words (C0-C377) as inputs (256 bits).  Function code=3.
drvModbusAsynConfigure("K1_Cn_In_Word",  "Koyo1", 1, 3, 040600, 020,    0,  100, "Koyo")

# We access the same 16 words (C0-C377) as outputs (256 bits). Function code=6.
drvModbusAsynConfigure("K1_Cn_Out_Word", "Koyo1", 1, 6, 040600, 020,    0,  1,  "Koyo")

# We access the same 16 words (C0-C377) as array outputs (256 bits). Function code=16.
drvModbusAsynConfigure("K1_Cn_Out_Word_Array", "Koyo1", 1, 16, 040600, 020,    0,   1, "Koyo")
</pre>
  <p>
    Note that this example is designed for testing and demonstration purposes, not as
    a realistic example of how <b>modbus</b> would normally be used. For example, it
    loads 6 drivers to access the C control relays using function codes 1 (read coils),
    3 (read holding registers), 5 (write single coil), 6 (write single holding register),
    15 (write multiple coils), and 16 (write multiple holding registers). This allows
    for testing of all function codes and record types, including waveforms. In practice
    one would normally only load at most 2 drivers for the C control relays, for example
    function code 1 (read coils), and function code 5 (write single coil).</p>
  <p>
    There is another example application in the iocTest directory, sim1.cmd and sim1.substutions.
    This example is used for testing the different Modbus data types. It was tested
    with the <a href="http://www.modbustools.com/modbus_slave.asp">Modbus Slave</a>
    program, which is an inexpensive Modbus slave emulator.</p>
  <h2 id="medm_screens">
    medm screens</h2>
  <p>
    <b>modbus</b> provides example medm .adl files in the modtcpApp/op/adl directory.
    The following are screen shots of these screens from an IOC controlling a Koyo DL205
    PLC.</p>
  <div style="text-align: center">
    <hr />
    <h3>
      Koyo1.adl</h3>
    <h3>
      Top level medm screen for the Koyo1 example application.</h3>
    <img alt="Koyo1.png" src="Koyo1.png" />
  </div>
  <div style="text-align: center">
    <hr />
    <h3>
      Koyo_8inputs.adl</h3>
    <h3>
      Inputs X0-X7 read as discrete inputs (function code 1).</h3>
    <img alt="K1_Xn_Bit.png" src="K1_Xn_Bit.png" />
  </div>
  <div style="text-align: center">
    <hr />
    <h3>
      Koyo_8outputs.adl</h3>
    <h3>
      Outputs Y0-Y7 written using register access (function code 6).</h3>
    <img alt="K1_Yn_Out_Word.png" src="K1_Yn_Out_Word.png" />
  </div>
  <div style="text-align: center">
    <hr />
    <h3>
      Koyo_8outputs.adl</h3>
    <h3>
      Outputs C200-C207 written using bit access (function code 5).</h3>
    <img alt="K1_C20n_Out_Bit.png" src="K1_C20n_Out_Bit.png" />
  </div>
  <div style="text-align: center">
    <hr />
    <h3>
      Koyo_8inputs.adl</h3>
    <h3>
      Inputs C200-C207 read as register inputs (function code 6).</h3>
    <img alt="K1_C20n_In_Word.png" src="K1_C20n_In_Word.png" />
  </div>
  <div style="text-align: center">
    <hr />
    <h3>
      modbusArray.adl</h3>
    <h3>
      Inputs C0-C377 read using a waveform record and coil access (function code 1).</h3>
    <img alt="K1_Cn_In_Bit_Array.png" src="K1_Cn_In_Bit_Array.png" />
  </div>
  <div style="text-align: center">
    <hr />
    <h3>
      modbusArray.adl</h3>
    <h3>
      Inputs C0-C377 read using a waveform record and register access (function code 3).</h3>
    <img alt="K1_Cn_In_Word_Array.png" src="K1_Cn_In_Word_Array.png" />
  </div>
  <div style="text-align: center">
    <hr />
    <h3>
      modbusStatistics.adl</h3>
    <h3>
      I/O statistics for the Modbus driver that is reading inputs X0-X37 using register
      access (function code 3). The histogram is the number of events versus TCP/IP write/read
      cycle time in msec.</h3>
    <img alt="K1_Xn_Bit_Statistics.png" src="K1_Xn_Bit_Statistics.png" />
  </div>
  <div style="text-align: center">
    <hr />
    <h3>
      Koyo2.adl</h3>
    <h3>
      Top level medm screen for the Koyo2 example application.</h3>
    <img alt="Koyo2.png" src="Koyo2.png" />
  </div>
  <div style="text-align: center">
    <hr />
    <h3>
      Koyo_4ADC.adl</h3>
    <h3>
      4 ADC inputs from a 13-bit bipolar ADC.</h3>
    <img alt="K2_ADCs.png" src="K2_ADCs.png" />
  </div>
  <h2 id="Debug_tracing">
    Debug tracing</h2>
  <p>
    One can obtain diagnostic output for a <b>modbus</b> port driver using the "dbior"
    or "asynPrint" commands at the iocsh or vxWorks shell. "asynReport" with no arguments
    will print a brief report for all asyn drivers, including the drvAsynIPPort or drvAsynSerialPort
    driver that <b>modbus</b> drivers are connected to, and for all <b>modbus</b> port
    drivers. For example, a partial output for the Koyo1 application when it is connected
    via TCP is:</p>
  <pre>epics> asynReport
Koyo1 multiDevice:No canBlock:Yes autoConnect:No
Port 164.54.160.158:502: Connected
K1_Xn_Bit multiDevice:Yes canBlock:No autoConnect:Yes
    addr 0 autoConnect Yes enabled Yes connected Yes exceptionActive No
    addr 1 autoConnect Yes enabled Yes connected Yes exceptionActive No
    addr 2 autoConnect Yes enabled Yes connected Yes exceptionActive No
    addr 3 autoConnect Yes enabled Yes connected Yes exceptionActive No
    addr 4 autoConnect Yes enabled Yes connected Yes exceptionActive No
    addr 5 autoConnect Yes enabled Yes connected Yes exceptionActive No
    addr 6 autoConnect Yes enabled Yes connected Yes exceptionActive No
    addr 7 autoConnect Yes enabled Yes connected Yes exceptionActive No
modbus port: K1_Xn_Bit
K1_Xn_Word multiDevice:Yes canBlock:No autoConnect:Yes
    addr 0 autoConnect Yes enabled Yes connected Yes exceptionActive No
</pre>
  <p>
    To obtain more detailed information, one can request information for a specific
    <b>modbus</b> port driver, and output level >0 as follows:</p>
  <pre>epics> asynReport 5, "K1_Xn_Word"
K1_Xn_Word multiDevice:Yes canBlock:No autoConnect:Yes
    enabled:Yes connected:Yes numberConnects 1
    nDevices 1 nQueued 0 blocked:No
    asynManagerLock:No synchronousLock:No
    exceptionActive:No exceptionUsers 0 exceptionNotifys 0
    interfaceList
        asynCommon pinterface 0x4001d180 drvPvt 0x8094f78
        asynDrvUser pinterface 0x4001d10c drvPvt 0x8094f78
        asynUInt32Digital pinterface 0x4001d118 drvPvt 0x8094f78
        asynInt32 pinterface 0x4001d134 drvPvt 0x8094f78
        asynFloat64 pinterface 0x4001d148 drvPvt 0x8094f78
        asynInt32Array pinterface 0x4001d158 drvPvt 0x8094f78
    addr 0 autoConnect Yes enabled Yes connected Yes exceptionActive No
    exceptionActive No exceptionUsers 1 exceptionNotifys 0
    blocked No
modbus port: K1_Xn_Word
    asyn TCP server:    Koyo1
    modbusFunction:     3
    modbusStartAddress: 040400
    modbusLength:       010
    plcType:            Koyo
    I/O errors:         0
    Read OK:            5728
    Write OK:           0
    pollDelay:          0.100000
    Time for last I/O   3 msec
    Max. I/O time:      12 msec
</pre>
  <p>
    To obtain run-time debugging output for a driver use the asynSetTraceMask and asynSetTraceIOMask
    commands. For example the following commands will show all I/O to and from the PLC
    from the underlying drvAsynIPPort driver:</p>
  <pre>epics> asynSetTraceIOMask "Koyo1",0,4   # Enable traceIOHex
epics> asynSetTraceMask "Koyo1",0,9     # Enable traceError and traceIODriver
epics> 
2007/04/12 17:27:45.384 164.54.160.158:502 write 12

00 01 00 00 00 07 ff 02 08 00 00 20 
2007/04/12 17:27:45.390 164.54.160.158:502 read 13

00 01 00 00 00 07 ff 02 04 00 00 00 00 
2007/04/12 17:27:45.424 164.54.160.158:502 write 12

00 01 00 00 00 07 ff 03 41 00 00 08 
2007/04/12 17:27:45.432 164.54.160.158:502 read 25

00 01 00 00 00 13 ff 03 10 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 
...
epics> asynSetTraceMask "Koyo1",0,1    # Turn off traceIODriver
</pre>
  <p>
    The following command shows the I/O from a specific <b>modbus</b> port driver:</p>
  <pre>epics> asynSetTraceIOMask "K1_Yn_In_Word",0,4   # Enable traceIOHex
epics> asynSetTraceMask "K1_Yn_In_Word",0,9     # Enable traceError and traceIODriver
epics> 
2007/04/12 17:32:31.548 drvModbusAsyn::doModbusIO port K1_Yn_In_Word READ_REGISTERS
09 00 00 00 00 00 00 00 
2007/04/12 17:32:31.656 drvModbusAsyn::doModbusIO port K1_Yn_In_Word READ_REGISTERS
09 00 00 00 00 00 00 00 
2007/04/12 17:32:31.770 drvModbusAsyn::doModbusIO port K1_Yn_In_Word READ_REGISTERS
09 00 00 00 00 00 00 00 
2007/04/12 17:32:31.878 drvModbusAsyn::doModbusIO port K1_Yn_In_Word READ_REGISTERS
09 00 00 00 00 00 00 00 
2007/04/12 17:32:31.987 drvModbusAsyn::doModbusIO port K1_Yn_In_Word READ_REGISTERS
09 00 00 00 00 00 00 00 
epics> asynSetTraceMask "K1_Yn_In_Word",0,1     # Disable traceIODriver
</pre>
  <p>
    One can also load an EPICS asyn record on a <b>modbus</b> port, and then use EPICS
    channel access to turn debugging output on and off. The following medm screen shows
    how to turn on I/O tracing using this method.</p>
  <div style="text-align: center">
    <hr />
    <h3>
      asynRecord.adl</h3>
    <h3>
      Using the asynRecord to turn on traceIODriver and traceIOHex for debugging.</h3>
    <img alt="K1_Yn_In_Word.png" src="K1_Yn_In_Word.png" />
  </div>
  <p>
    The asyn record can also be used to perform actual I/O to the PLC. For example the
    following screen shots shows the asyn record being used to control output Y1 on
    a PLC. Note that the ADDR field is set to 1 (to select Y1) and the data set to 1
    (to turn on the output). Each time the asyn record is processed the value will be
    sent to the PLC.</p>
  <div style="text-align: center">
    <hr />
    <h3>
      asynRecord.adl</h3>
    <h3>
      Using the asynRecord to perform actual I/O to a PLC. Note that Address (ADDR)=1.</h3>
    <img alt="K1_Yn_Out_Bit_Asyn.png" src="K1_Yn_Out_Bit_Asyn.png" />
  </div>
  <div style="text-align: center">
    <hr />
    <h3>
      asynRegister.adl.adl</h3>
    <h3>
      Using the asynRecord to perform actual I/O to a PLC. Note that Interface (IFACE)=asynUInt32Digital,
      Transfer (TMOD)=Write, and Output (UI32OUT)=1. This value will be written to the
      Y1 output when the record is processed.</h3>
    <img alt="K1_Yn_Out_Bit_AsynRegister.png" src="K1_Yn_Out_Bit_AsynRegister.png" />
  </div>
  <h2 id="Differences_from_Triumf_modtcp_and_plctcp_package">
    Differences from Triumf modtcp and plctcp package</h2>
  <p>
    The following are the main enhancements of <b>modbus</b> compared to the modtcp
    and plctcp packages from Triumf:</p>
  <ul>
    <li><b>modbus</b> supports serial RTU and serial ASCII in addition to TCP. modtcp
      supports only TCP.</li>
    <li><b>modbus</b> supports most Modbus function codes, including 1, 2, 5, and 15 for
      reading and writing coils (single bits) and 3, 4, 6, and 16 for reading and writing
      registers. modtcp supports only Modbus functions 3 (read holding register) and 6
      (write holding register).</li>
    <li><b>modbus</b> supports pseudo-interrupt mode on inputs. The poller thread calls
      device support when there is a new value, so the EPICS records can have SCAN=I/O
      Intr and do not need to periodically read the inputs. This is more efficient.</li>
    <li><b>modbus</b> supports array operations on reads and writes using waveform records.</li>
    <li><b>modbus</b> uses asyn for underlying TCP/IP connections. This reduces the amount
      of code, and has good debugging and connection management.</li>
    <li><b>modbus</b> uses standard asyn interfaces and asyn device support. This reduces
      the amount of code significantly.</li>
    <li><b>modbus</b> should run on all EPICS platforms. It has been tested on linux-x86,
      vxWorks, win32-x86 (native Windows with VC++ compiler), and cygwin-x86 (Windows
      with gcc compiler and Cygwin library). modtcp only works on Linux and vxWorks.</li>
    <li><b>modbus</b> provides EPICS PVs for PLC status and statistics.</li>
  </ul>
  <p>
    The following are some drawbacks of <b>modbus</b> compared to the modtcp and plctcp
    packages from Triumf:</p>
  <ul>
    <li><b>modbus</b> only works on EPICS 3.14, not on 3.13.</li>
    <li>There is no Siemens PLC support. It should be a separate driver, using drvModbusAsyn.c
      as a model. Most code in <b>modbus</b> is Modbus specific, so it is cleaner to write
      a separate, but similar driver for other protocols.</li>
    <li><b>modbus</b> has one thread per asyn port, which is a group of Modbus registers
      or coils. This is more threads than modtcp, which had only 1 thread per PLC. There
      is an advantage in the <b>modbus</b> design, because each can poll at a different
      rate, can have different Modbus function code, and a different data type. It also
      allows much finer granularity in debugging output. But it will lead to somewhat
      increased resource usage compared to the modtcp design.</li>
  </ul>
  <h2 id="Known_problems">
    Known problems</h2>
  <ul>
    <li>No known problems at this time.</li>
  </ul>
</body>
</html>
